# 节点的数据更新，节点的网络共识
import hashlib  # 信息安全加密
import json  # 互联网传递信息的一种格式import time#时间模块
import time
from urllib.parse import urlparse  # 网络编码解码from uuid import uuid4#生成唯一序列号
from uuid import uuid4

import requests  # 网络请求
from flask import Flask, jsonify, request  # 请求，网络请求
from typing import Any, Dict, List, Optional  # 数据结构


class DaDaCoinBlockChain:
    def __init__(self):  # 初始化
        self.current_transactions = []  # 交易列表
        self.chain = []  # 区块链管理多个区块
        self.nodes = set()  # 保存网络中其他节点
        self.new_block(previous_hash="1", proof=100)  # 创建创世区块

    def new_block(self,
                  proof: int,  # 确定proof为int类型
                  previous_hash: Optional[str]  # 上一块的哈希类型
                  ) -> Dict[str, Any]:  # 创建一个区块，返回一个字典数据类型
        block = {
            "index": len(self.chain) + 1,  # 索引
            "timestamp": time.time(),  # 当前时间
            "transaction": self.current_transactions,  # 交易记录
            "proof": proof,  # 工作量证明
            "previous_hash": previous_hash or self.hash(self.chain[-1])  # 前一区块哈希
        }
        self.current_transactions = []  # 交易记录加入区块之后清空
        self.chain.append(block)  # 区块加入区块链
        return block

    def new_transactions(self, sender: str, recipient: str, amount) -> int:  # 创建一个交易
        self.current_transactions.append({
            "sender": sender,  # 付款方
            "recipient": recipient,  # 收款方
            "amount": amount  # 数量
        })
        return self.last_block["index"] + 1  # 索引标记交易数量

    @property
    def last_block(self) -> Dict[str, Any]:  # 取得最后一个区块
        return self.chain[-1]

    @staticmethod
    def hash(block: Dict[str, any]) -> str:  # 哈希加密，传递一个字典，返回字符串
        blockString = json.dumps(block, sort_keys=True).encode()  # 编码
        return hashlib.sha256(blockString).hexdigest()

    def proof_of_work(self, last_proof: int) -> int:  # 工作量证明，挖矿过程
        proof = 0
        while self.valid_proof(last_proof, proof) is False:
            proof += 1
        return proof

    @staticmethod  # 第N个区块依赖于N-1个区块，简单挖矿
    def valid_proof(last_proof: int, proof: int) -> bool:  # 验证证明
        guess = f'{last_proof * proof}'.encode()
        guess_hash = hashlib.sha256(guess).hexdigest()
        # print(str(guess_hash))
        return guess_hash[-4:] == "1234"

    def valid_chain(self, chain: List[Dict[str, Any]]) -> bool:  # 区块链校验
        # List[Dict[str, Any]]是一个列表，列表的每个元素都是字典
        last_block = chain[0]  # 第一个区块
        curr_index = 1  # 当前的第一个索引
        while curr_index < len(chain):
            block = chain[curr_index]  # 当前区块
            # 哈希校验，校验区块链的链接
            if block["previous_hash"] != self.hash(last_block):
                return False
            # 工作量校验，挖矿的工作量校验
            if not self.valid_proof(last_block["proof"], block["proof"]):
                return False
            last_block = block  # 轮询
            curr_index += 1  # 索引自增

        return True

    def register_node(self, addr: str) -> None:  # 加入网络的其他节点，用于更新
        now_url = urlparse(addr)  # 解析
        self.nodes.add(now_url.netloc)  # 增加网络节点

    def resolve_conflicts(self) -> bool:  # 共识算法
        # 网络中的多个节点，取出最长的
        neighbours = self.nodes  # 取得所有的节点
        new_chain = None  # 新的区块链
        max_length = len(self.chain)  # 当前的区块链长度
        for node in neighbours:
            response = requests.get(f"http://{node}/chain")  # 访问网络节点
            if response.status_code == 200:
                length = response.json()["length"]  # 取出长度
                chain = response.json()["chain"]  # 取出区块链
                # 如果当前区块链比我长并且经得起校验，那么就更新
                if length > max_length and self.valid_chain(chain):
                    max_length = length
                    new_chain = chain  # 保存长度与区块链
        if new_chain:
            self.chain = new_chain  # 替换区块链
            return True
        return False


dadacoin = DaDaCoinBlockChain()  # 创建一个网络节点
node_id = str(uuid4()).replace("-", "")  # 节点替换，生成密钥
print("当前钱包地址：", node_id)
app = Flask(__name__)  # 初始化flask框架


@app.route("/")
def index_page():
    return "你好，欢迎来到达达币系统！"


@app.route("/chain")  # 查看所有的区块链
def index_chain():
    response = {
        "chain": dadacoin.chain,  # 区块链
        "length": len(dadacoin.chain)  # 区块链长度
    }
    return jsonify(response), 200


@app.route("/mine")  # 挖矿
def index_mine():
    last_block = dadacoin.last_block  # 取得最后一个区块
    last_proof = last_block["proof"]  # 取得工作量证明
    proof = dadacoin.proof_of_work(last_proof)  # 挖矿计算

    # 系统奖励比特币，挖矿产生交易
    dadacoin.new_transactions(
        sender="0",  # 0代表系统奖励
        recipient=node_id,  # 当前钱包地址
        amount=10  # 奖励数量
    )
    block = dadacoin.new_block(proof, None)  # 增加一个区块
    response = {
        "message": "新的区块创建",
        "index": block["index"],  # 仓建的索引
        "transaction": block["transaction"],  # 交易
        "proof": block["proof"],  # 工作量证明
        "previous_hash": block["previous_hash"]  # 上一块的哈希


    }
    return jsonify(response), 200


@app.route("/new_transaction", methods=["POST"])  # 创建一个新的交易
def new_transaction():
    values = request.get_json()  # 抓取网络传输的信息
    required = ["sender", "recipient", "amount"]
    if not all(key in values for key in required):
        return "数据不完整", 400
    index = dadacoin.new_transactions(values["sender"],
                                      values["recipient"],
                                      values["amount"])  # 新增一个交易

    response = {
        "message": f"交易加入到区块{index}",
    }
    return jsonify(response), 200


@app.route("/new_node", methods=["POST"])   # 增加网络节点
def new_node():
    values = request.get_json() # 获取json字符串
    nodes = values.get("nodes") # 获取所有节点
    if nodes is None:
        return "节点为空", 400
    for node in nodes:
        dadacoin.register_node(node)    # 增加网络节点

    response = {
        "message": "网络节点已经增加",
        "nodes": list(dadacoin.nodes)   # 查看所有节点
    }
    return jsonify(response), 200


@app.route("/node_refresh")
def node_refresh():
    replaced = dadacoin.resolve_conflicts() # 共识算法进行最长替换
    message = ""
    if replaced:
        message += "区块链已经被替换为最长"
    else:
        message += "当前区块链已经是最长无需替换"

    response = {
        "message": message,
        "new--chain": dadacoin.chain
    }
    return jsonify(response), 200


if __name__ == '__main__':
    app.run("127.0.0.1", 5000)
